Repository navigation
[DCP] Run fi_a2a on FlashInfer's fused all-to-all + LSE reduce - #40539
Draft
thanhhao98 wants to merge 13 commits into
Draft
thanhhao98 wants to merge 13 commits into
thanhhao98 wants to merge 13 commits into
Conversation
…E reduce The existing a2a and fi_a2a backends exchange head partials and then merge them with a separate Triton LSE kernel, so every MLA layer round-trips the partials through global memory between the two kernels. FlashInfer's fused decode-CP op does the exchange and the LSE-weighted reduce inside one cooperative kernel over NCCL device-side LSA symmetric memory, removing that round-trip and one launch per layer per decode step. Expose it as a fourth --dcp-comm-backend value rather than changing fi_a2a in place, so the validated MNNVL path is untouched and the two can be compared directly. Resolution never selects it on its own; it is explicit-only. The fused op allows one workspace per ordered CUDA stream, so the workspaces are allocated upfront (rendezvous is collective and not graph-capturable) and handed out per stream: one for eager decode, one for the global graph-capture stream. Requires a FlashInfer build carrying the op (flashinfer-ai/flashinfer#4929), NCCL >= 2.29 with device-communicator support, and a DCP group inside one NVLink domain. fp16/bf16 only -- unlike a2a, which ships partials as opaque bytes, the fused kernel has no fp8 path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
thanhhao98
force-pushed
the
htphan/dcp-fi-a2a-fused
branch
from
September 21, 2026 08:47
9ef5f8f to
852d660
Compare
added 5 commits
September 30, 2026 01:02
…A2A + LSE reduce" This reverts commit 852d660.
fi_a2a is moving to FlashInfer's decode_cp_a2a_lse_reduce, which needs torch >= 2.14, a FlashInfer build that ships the op, an MLA model and a DCP group inside one NVLink domain. It has no fallback kernel, so an unset --dcp-comm-backend must only resolve to fi_a2a where all of that holds, and otherwise to a2a. fi_a2a_platform_blocker reports the host-side reason without importing FlashInfer or creating a CUDA context; fi_a2a_blocker adds the configuration checks (MLA, PDMUX, torch symm-mem all-reduce, the PP DeepGEMM warmup stream, an exported NCCL_CUMEM_ENABLE=0). An explicit fi_a2a names the blocker, and otherwise defaults NCCL_CUMEM_ENABLE=1 because the fused workspace is an NCCL symmetric window.
fi_a2a exchanged DCP head partials with FlashInfer's MNNVL all-to-all and then merged them with a separate Triton LSE kernel, so every MLA layer round-tripped the partials through HBM between two launches. decode_cp_a2a_lse_reduce does the exchange and the LSE-weighted merge in one cooperative kernel over NCCL LSA symmetric memory; fi_a2a now uses it exclusively. Before graph capture, every DCP rank agrees over gloo that the op can run, then creates two workspaces (one per ordered CUDA stream: graph capture and serving) and checks one probe call per LSE base against an all-gather + Triton reference. That probe also makes the group's first, collective device-comm setup eager. Batches wider than the captured graphs run as row chunks on the same stream and workspace. The op is wrapped as a custom op so Dynamo does not trace the stream-to-workspace lookup. The workspace comes from torch symmetric memory on the NCCL backend, a process-global choice frozen by the first symmetric allocation, so bootstrap selects it before the groups are built. CustomAllReduceV2 cannot allocate from that backend (it passes a group name and rendezvouses on gloo), so it yields to CustomAllreduce there.
Describe what fi_a2a now requires (Blackwell, one NVLink domain, an MLA model, torch >= 2.14, a FlashInfer build with decode_cp_a2a_lse_reduce), how an unset --dcp-comm-backend resolves, and the process-wide settings it changes: NCCL symmetric-memory backend with custom all-reduce v1, NCCL_CUMEM_ENABLE=1, and PDMUX / torch symm-mem all-reduce rejected.
|
Hi, @thanhhao98 flashinfer-ai/flashinfer#4929 has been merged. |
added 7 commits
October 6, 2026 15:59
FlashInfer's fused DCP reduce allocates its workspace on whichever torch symmetric-memory backend is active. Stop selecting NCCL at bootstrap, and drop the custom all-reduce v1 fallback, the NCCL_CUMEM_ENABLE default and the cuMem, torch 2.14, NCCL 2.29 and --enable-torch-symm-mem checks that only that backend needed. No per-rank environment check is left to agree on, so remove the startup probe and its gloo vote. Keep one workspace for graphs captured on the capture stream and one for eager calls, chosen by the current stream, and re-raise a failed workspace creation with what to change.
The fused reduce accepts strided [B, H/cp, cp, D] and [B, H/cp, cp] views, so view the attention output and LSE instead of packing them. Head h still goes to peer h // (H/cp). Inputs with a non-unit inner stride, or rows off an 8-byte boundary, are copied first; wide eager batches still run as token slices. Drop the pack test for the layout only fi_a2a used.
fi_a2a needs Blackwell, an MLA model, a DCP group inside one NVLink domain and a flashinfer-python with the fused decode_cp_a2a_lse_reduce; it no longer changes the symmetric-memory backend, custom all-reduce or NCCL settings, so drop those notes. Describe the two workspaces, the chunked eager batches and the configurations that resolve to a2a.
The DCP docs now give the size of the two fused-reduce workspaces: they follow --cuda-graph-max-bs-decode, also with decode CUDA graphs disabled, and come out of the memory left after KV-cache sizing. The Kimi K3 notes name the FlashInfer op the default backend needs. Drop the copy for strided or misaligned partials: the only caller passes contiguous ones, and FlashInfer checks its inputs. Comments that still described fi_a2a as packing and running the Triton combine now describe a2a only. Tests: keep the bf16 reference cases that cover the head mapping and chunking, and drop the cases that only exercised FlashInfer or the removed copy. Fail rather than skip when custom all-reduce v2 should be running. Add the PP case that keeps fi_a2a without the DeepGEMM warmup.
…rves the group Cross-node groups use custom all-reduce v2 only when they form one NVLink clique on a VMM-backed allocator, so the coexistence case now asks the same predicate the dispatcher uses and skips otherwise.
|
Hi @thanhhao98, is there anything I can help with to move this PR forward? |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
--dcp-comm-backend fi_a2a(the Blackwell default for MLA decode context parallelism) runs three kernels per MLA layer perdecode step:
dcp_pack_a2a_sendpacks the attention output;helixAllToAllKernelexchanges it;_dcp_lse_combine_kernelmerges it.FlashInfer's
decode_cp_a2a_lse_reducedoes the exchange and the LSE-weighted merge in one kernel:on whichever torch symmetric-memory backend the process uses.
This PR makes
fi_a2ause that op, and only it.become one.
CustomAllReduceV2. Nothingelse in the process changes: no backend switch, custom all-reduce v2 stays on, no
NCCL_CUMEM_ENABLEchange.Dependency. This needs a flashinfer-python release that contains flashinfer-ai/flashinfer#5726. sglang currently pins
0.7.0.post1, which has no fused op; the release candidates so far (0.7.1rc1–rc5, 0.7.2rc1) have #4929's version, which
forces the NCCL backend. This PR stays draft until the FlashInfer bump lands. Until then:
--dcp-comm-backendresolves toa2aand logs why;fi_a2ais rejected at argument resolution;Modifications
fi_a2a= the fused kernel, exclusively. The MNNVL exchange + Triton merge path is removed.init_fi_a2a_workspace):CUDA stream;
--cuda-graph-max-bs-decode, times the tokens each request verifiesunder speculative decoding), also with
--disable-decode-cuda-graph;DCP fi_a2a: fused all-to-all + LSE reduce ready (backend=..., capacity ..., 2 workspaces x ... MiB)once.fi_a2a_lse_reduce, a registered custom op, sotorch.compilesees one opaque collective):[B, H, D]attention output as[B, H/cp, cp, D]and its LSE as[B, H/cp, cp](no copy); headhgoes topeer
h // (H/cp), exactly as the pack kernel sent it;lse_modefrom the attention backend;fi_a2a_platform_blocker: Blackwell, the DCP group inside one NVLink domain (GB200/GB300 fabric or one node), and aFlashInfer that provides the op. It never imports FlashInfer.
fi_a2a_blockeradds an MLA model, no--enable-pdmux, and no PP DeepGEMM warmup stream at--pp-size > 1.fi_a2aonly where it can run, elsea2a, and logs the reason. An explicitfi_a2anames the blocker.dcp.mdx,server_arguments.mdx, the--dcp-comm-backendhelp, and the Kimi-K3 cookbook note.Accuracy Tests
test/registered/kernels/ops/communication/test_dcp_fi_a2a_reduce.py, at 2 / 4 / 8 × B200: 10 passed,0 skipped at each size. It checks:
batch) and 2 × 49;
[B, H, 1]LSE;is skipped because sglang does not build custom all-reduce v2 for that group). DeepSeek-V2-Lite TP 8 = DCP 8: GSM8K 0.640
for both
fi_a2aanda2a, and TPOT at concurrency 1 is 2.3% lower withfi_a2a.--parallel 1) on every serving arm below: main 0.80 and this PR 0.76 in every run(2 of 50 questions; greedy decoding repeats exactly). The full question set is below.
--parallel 32):--cuda-graph-max-bs-decode 4: this PR 0.772, main 0.766;Speed Tests and Profiling
8× B200, DeepSeek-V2-236B BF16, TP 8 = DCP 8, FlashInfer MLA, 256K context, image
lmsysorg/sglang:nightly-dev-cu13-20261006-f3fe7534(torch 2.14.1, sglang-kernel 0.4.9). FlashInfer 0.7.0.post1 with the op from flashinfer-ai/flashinfer#5726 laid over it, the
same for every arm. Default settings in every arm (custom all-reduce v2 on).
Per MLA layer (microbenchmark, 16 heads per rank,
head_dim512, 60 calls per CUDA graph, max over ranks of each rank'smedian, µs):
L2 hot, mean of two repeats in one job (palindromic order); with L2 flushed before every layer, 9.51 → 7.25 µs at batch 1.
serving: 0.26 + 0.60 µs).
torch#4929), same job: 27.4 µs at batch 1 and 37.5 µs at batch 32, on contiguous input.Serving (median TPOT, ms, ISL 260992 / OSL 1024). Each job runs main, this PR, this PR, main (or the reverse) on one
node:
Decode-only step time at 32K context (
one_batch_server), batch 8 / 16 / 32: −1.19 / −1.14 / −1.52%, lower in all 4jobs.
Concurrency-4 TPOT is noisy: only 9 requests finish per run, and TPOT includes stalls behind other requests'
261K-token prefills.
this PR.
Control (
a2aon main vs this PR, untouched by this change): +0.05% TPOT at concurrency 1.vs
a2a(why the resolver keeps preferringfi_a2a): TPOT at concurrency 1 is 5.1% lower withfi_a2a(13.16 vs13.86 ms).
Why the FlashInfer version matters. FlashInfer's current op ([Bug] sglang frontend has an incorrect dependency on
torch#4929, in the 0.7.1 / 0.7.2 release candidates) cannotstart next to custom all-reduce v2. With v1 all-reduce in every arm, TPOT at concurrency 1 is:
torch#4929's op 15.658 ms (+14.5%), with the inputs copied contiguous;nsys puts the DCP time per decode step at 568, 2534 and 445 µs respectively.
nsys (20 decode steps at concurrency 1; two captures in opposite orders; kernels per decode step on each of the 8 GPUs):
_dcp_pack_a2a_send_kernelhelixAllToAllKernel_dcp_lse_combine_kernelflashinfer::comm::dcp::FusedKernel(base-2)399–410 µs.
--cuda-graph-max-bs-decode 32. They grow with the widest captured decodebatch times the local heads, and are allocated after KV-cache sizing, so
max_total_num_tokensis unchanged (11,465,208 inboth arms).
dcp.mdxdocuments the size.Coordination
fi_a2amemory term assumes the MNNVL workspace. The fused workspace is sized bydecode_cp_a2a_lse_reduce_workspace_size(...)(two of them), and wider batches are chunked.a2abranch ofdcp_a2a_lse_reduce.dcp.mdx,test_server_args.py).Checklist
CI States
Latest PR Test (Base): ❌ Run #37612939714
Latest PR Test (Extra): ❌ Run #37612939327
Latest PR Test (AMD ROCm 10): ❌ Run #37612939559